#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

//#define DO_DUMP

int mem[1024 * 1024];

typedef enum {
    OP_PUSH,
    OP_POP,
    OP_MOV,
    OP_ADD,
    OP_SUB,
    OP_MUL,
    OP_DIV,
    OP_MOD,
    OP_CMP,
    OP_JMP,
    OP_JE,
    OP_JNE,
    OP_JL,
    OP_JG,
    OP_JLE,
    OP_JGE,
    OP_JZ,
    OP_JNZ,
    OP_CALL,
    OP_RET,
    OP_NOP,
    OP_IN,
    OP_OUT,
    OP_HLT,
} OpCode;

typedef enum {
    EAX,
    EBX,
    ECX,
    EDX,
    EBP,
    EDI,
    ESI,
    ESP,
} RegType;

typedef enum {
    REG,
    NUM,
    MEM,
} OperandType;

typedef struct {
    int *eip, *stack, *codebase;
    int reg[8];
    int cond;
} VM;

typedef enum {
    IN,
    OUT
} PortType;

typedef struct {
    void *func;
    PortType type;
} portFunc;

typedef int (*inPort)();
typedef void (*outPort)(int);

#define MAX_PORT_NUM 3

portFunc handlers[MAX_PORT_NUM];

int fvm_getchar()
{
    return getchar();
}

void fvm_putchar(int c)
{
    putchar(c);
}

void registerPortFunc(int port, void *func, PortType type)
{
    portFunc f = {func, type};
    handlers[port] = f;
}

VM newVM()
{
    VM vm;
    memset(vm.reg, 0, sizeof(vm.reg));
    vm.cond = -2;
    vm.codebase = vm.eip = (int *) malloc(4); // temp memory
    memset(vm.codebase, 0, 4);
    registerPortFunc(0, fvm_getchar, IN);
    registerPortFunc(1, fvm_putchar, OUT);
    return vm;
}

void loadCommand(VM *vm, int *command, int len)
{
    memcpy(mem, command, len);
}

#define MODIFY_REG(opcode, op) \
    case (opcode): { \
        int first_type = *(++vm->eip); \
        if (first_type == REG) {\
            int start_reg = *(++vm->eip); \
            int type = *(++vm->eip); \
            if (type == REG) { \
                int end_reg = *(++vm->eip); \
                vm->reg[start_reg] op vm->reg[end_reg]; \
            } else if (type == NUM) { \
                int num = *(++vm->eip); \
                vm->reg[start_reg] op num; \
            } else if (type == MEM) { \
                int addr = *(++vm->eip);\
                vm->reg[start_reg] op mem[addr]; \
            } \
            if (start_reg == ESP) { \
                vm->stack = (int *) vm->reg[start_reg];\
            } \
        } else if (first_type == MEM) {\
            int addr = *(++vm->eip); \
            int type = *(++vm->eip); \
            if (type == REG) { \
                int reg = *(++vm->eip); \
                mem[addr] op vm->reg[reg]; \
            } else if (type == NUM) { \
                int num = *(++vm->eip); \
                mem[addr] op num; \
            } else if (type == MEM) { \
                int addr2 = *(++vm->eip);\
                mem[addr] op mem[addr2]; \
            } \
        } else if (first_type == NUM) {\
            printf("\nERROR: the first operand of %s cannot be a number", #opcode); \
            return; \
        } \
        break; \
    }

#define DO_JUMP_WHEN(opcode, condition) \
		case (opcode): { \
            int type = *(++vm->eip); \
            if (type == REG) { \
                int dist_reg = (*++vm->eip); \
                if (condition) vm->eip = &vm->codebase[vm->reg[dist_reg]]; \
            } else if (type == NUM) { \
                int dist_addr = (*++vm->eip); \
                if (condition) vm->eip = &vm->codebase[dist_addr]; \
            } \
            continue; \
        }

void do_push(VM *vm, int val)
{
    int *esp = (int *) vm->reg[7];
    *(++esp) = val;
    vm->reg[7] += 4;
}

int do_pop(VM *vm)
{
    int *esp = (int *) vm->reg[7];
    int val = *(esp--);
    vm->reg[7] -= 4;
    return val;
}

int sgn(int i)
{
    if (i == 0) return 0;
    return i > 0 ? 1 : -1;
}

void executeCommand(VM *vm)
{
    vm->codebase = vm->eip = mem;

    for (;;) {
        switch (*(vm->eip)) {
        	// stack
            case OP_PUSH: {
                int type = *(++vm->eip);
                if (type == NUM) {
                    do_push(vm, *(++vm->eip));
                } else if (type == REG) {
                    do_push(vm, vm->reg[*(++vm->eip)]);
                }
                break;
            }
            case OP_POP: {
                int val = do_pop(vm);
                int reg = *(++vm->eip);
                vm->reg[reg] = val;
                break;
            }
            // arithmetic
            MODIFY_REG(OP_MOV, =)
            MODIFY_REG(OP_ADD, +=)
            MODIFY_REG(OP_SUB, -=)
            MODIFY_REG(OP_MUL, *=)
            MODIFY_REG(OP_DIV, /=)
            MODIFY_REG(OP_MOD, %=)
            // control flow
            case OP_CMP: {
                int start_reg = *(++vm->eip);
                int type = *(++vm->eip);
                if (type == REG) {
                    int end_reg = *(++vm->eip);
                    vm->cond = sgn(vm->reg[start_reg] - vm->reg[end_reg]);
                } else if (type == NUM) {
                    int num = *(++vm->eip);
                    vm->cond = sgn(vm->reg[start_reg] - num);
                } else if (type == MEM) {
                    printf("ERROR: the second operand of OP_CMP must not be an memory address");
                }
                break;
            }
            DO_JUMP_WHEN(OP_JMP, 1)
            DO_JUMP_WHEN(OP_JE, vm->cond == 0)
            DO_JUMP_WHEN(OP_JL, vm->cond == -1)
            DO_JUMP_WHEN(OP_JG, vm->cond == 1)
            DO_JUMP_WHEN(OP_JLE, vm->cond == -1 || vm->cond == 0)
            DO_JUMP_WHEN(OP_JGE, vm->cond == 0 || vm->cond == 1)
            DO_JUMP_WHEN(OP_JNE, vm->cond != 0)
            DO_JUMP_WHEN(OP_JZ, vm->cond == 0)
            DO_JUMP_WHEN(OP_JNZ, vm->cond != 0)
            // func
            case OP_CALL: {
                int type = *(++vm->eip);
                if (type == REG) {
                    int dist_reg = (*++vm->eip);
                    do_push(vm, (int) (vm->eip + 1));
                    vm->eip = &vm->codebase[vm->reg[dist_reg]];
                } else if (type == NUM) {
                    int dist_addr = (*++vm->eip);
                    do_push(vm, (int) (vm->eip + 1));
                    vm->eip = &vm->codebase[dist_addr];
                }
                continue;
            }
            case OP_RET: {
                int prev_eip = do_pop(vm);
                vm->eip = (int *) prev_eip;
                continue;
            }
            // nop
            case OP_NOP: {
				break;
			}
            // in&out
            case OP_IN: {
                int reg_to = *(++vm->eip);
                if (reg_to != EAX) {
                    printf("\nERROR: the first operand of OP_IN must be EAX");
                    return;
                }
                int type = *(++vm->eip);
                int port_num = -114514;
                if (type == REG) {
                    int port_reg = *(++vm->eip);
                    if (port_reg != EDX) {
                        printf("\nERROR: the second operand of OP_IN must be EDX or a number");
                        return;
                    }
                    port_num = vm->reg[port_reg];
                } else if (type == NUM) {
                    port_num = *(++vm->eip);
                } else if (type == MEM) {
                    printf("\nERROR: the second operand of OP_IN must be EDX or a number");
                    return;
                }
                if (port_num >= MAX_PORT_NUM) {
                    printf("\nERROR: the port %d does not exist", port_num);
                }
                portFunc f = handlers[port_num];
                if (f.type != IN) {
                    printf("\nERROR: the port %d does not have a handler for OP_IN", port_num);
                    return;
                }
                inPort func = (inPort) (f.func);
                vm->reg[reg_to] = func();
                break;
            }
            case OP_OUT: {
                int port_num = -114514;
                int type = *(++vm->eip);
                if (type == NUM) {
                    port_num = *(++vm->eip);
                } else if (type == REG) {
                    int port_reg = *(++vm->eip);
                    if (port_reg != EDX) {
                        printf("\nERROR: the first operand of OP_OUT must be EDX or a number");
                        return;
                    }
                    port_num = vm->reg[port_reg];
                } else {
                    printf("\nERROR: the first operand of OP_OUT must be EDX or a number");
                    return;
                }
                int reg_from = *(++vm->eip);
                if (reg_from != EAX) {
                    printf("\nERROR: the second operand of OP_OUT must be EAX");
                    return;
                }
                if (port_num >= MAX_PORT_NUM) {
                    printf("\nERROR: the port %d does not exist", port_num);
                }
                portFunc f = handlers[port_num];
                if (f.type != OUT) {
                    printf("\nERROR: the port %d does not have a handler for OP_OUT", port_num);
                    return;
                }
                outPort func = (outPort) (f.func);
                func(vm->reg[reg_from]);
                break;
            }
            // hlt (for now just kill the program)
            case OP_HLT:
                return;
            default:
                printf("\nERROR: Undefined Opcode %d", *(vm->eip));
                return;
        }
        vm->eip++;
    }
}

void dumpCPU(VM *vm)
{
#ifdef DO_DUMP
    printf("\n-------------------\n");
    printf("[registers]\neax: %d; ebx: %d; ecx: %d; edx: %d; ebp: %d; edi: %d; esi: %d; cond: %d; esp: 0x%x; eip: 0x%x\n[memory]\nstack: [ ", vm->reg[0], vm->reg[1], vm->reg[2], vm->reg[3], vm->reg[4], vm->reg[5], vm->reg[6], vm->cond, vm->reg[7], vm->eip);
    for (int *stack = vm->stack + 1; stack <= (int *) vm->reg[7]; stack++) {
        printf("0x%x ", *stack);
    }
    printf("]\n");
    printf("[codes]\n");
    for (int *code = vm->codebase; code <= vm->eip; code++) {
        printf("%02x ", *code);
    }
    printf("\n-------------------\n");
#endif
}

int command[114514] = {
	OP_MOV, REG, ESP, NUM, (int) mem
};

void readFile(char *filename, int *buf)
{
	FILE *file = fopen(filename, "rb");
	
	fseek(file, 0L, SEEK_END);
	size_t fileSize = ftell(file);
	rewind(file);
	
	fread(buf, sizeof(int), fileSize, file);
	
	fclose(file);
}

int main(int argc, char **argv)
{
	if (argc == 1 || argc > 2) {
		printf("Usage: %s code.bin", argv[0]);
		return 0;
	}
	char *filename = argv[1];
	readFile(filename, command + 5);
	
    VM vm = newVM();
    dumpCPU(&vm);
    loadCommand(&vm, command, sizeof(command));
    dumpCPU(&vm);
    executeCommand(&vm);
    dumpCPU(&vm);
    return 0;
}